package com.zeyun.EasyOnlineShareCloud.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zeyun.EasyOnlineShareCloud.common.convention.exception.ServiceException;
import com.zeyun.EasyOnlineShareCloud.common.enums.ResultErrorCodeEnums;
import com.zeyun.EasyOnlineShareCloud.dao.entity.FileDO;
import com.zeyun.EasyOnlineShareCloud.dao.entity.RecordDO;
import com.zeyun.EasyOnlineShareCloud.dao.mapper.FileMapper;
import com.zeyun.EasyOnlineShareCloud.dto.resp.FileUploadResp;
import com.zeyun.EasyOnlineShareCloud.service.FileService;
import com.zeyun.EasyOnlineShareCloud.service.RecordService;
import com.zeyun.EasyOnlineShareCloud.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 文件服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl extends ServiceImpl<FileMapper, FileDO> implements FileService {
	private final FileUtil fileUtil;
	private final RecordService recordService;

	@Override
	@Transactional
	public FileUploadResp upload(MultipartFile file, String subPath, String specialPrefix, String IP) {
		String userId = StpUtil.getLoginIdAsString();
		try {
			// 文件上传并保存到数据库
			FileDO fileDO = fileUtil.uploadAndSaveFile(file, userId);

			// 确保 accessCode 唯一
			ensureUniqueAccessCode(fileDO);

			save(fileDO);

			// 保存记录
			RecordDO recordDO = RecordDO.builder()
					.genre("up")
					.fileId(fileDO.getId())
					.userId(StpUtil.getLoginIdAsString())
					.ip(IP)
					.build();
			recordService.save(recordDO);

			log.info("文件上传成功，文件ID: {}, AccessCode: {}", fileDO.getId(), fileDO.getAccessCode());
			return BeanUtil.toBean(fileDO, FileUploadResp.class);
		} catch (ServiceException e) {
			log.error("文件上传失败，用户ID: {}, 错误信息: {}", userId, e.getMessage(), e);
			throw new ServiceException(e.getMessage());
		} catch (Exception e) {
			log.error("文件上传发生未知错误，用户ID: {}, 错误信息: {}", userId, e.getMessage(), e);
			throw new ServiceException(ResultErrorCodeEnums.FILE_UPLOAD_FAILED);
		}
	}

	@Override
	@Transactional
	public Map<String, Object> download(String accessCode, String fileId, String IP) {
		log.info("收到下载文件请求，访问码: {}, 文件ID: {}", accessCode, fileId);
		InputStream inputStream = null;
		try {
			// 验证访问码
			if (accessCode == null || accessCode.isEmpty()) {
				throw new ServiceException("访问码不能为空");
			}

			// 构建查询条件
			LambdaQueryWrapper<FileDO> queryWrapper = new LambdaQueryWrapper<FileDO>()
					.eq(FileDO::getAccessCode, accessCode);

			// 如果提供了文件ID，则添加到查询条件
			if (fileId != null && !fileId.isEmpty()) {
				queryWrapper.eq(FileDO::getId, fileId);
			}

			// 查询文件信息
			FileDO fileDO = getOne(queryWrapper);

			if (fileDO == null) {
				log.error("文件不存在，访问码: {}, 文件ID: {}", accessCode, fileId);
				throw new ServiceException("文件不存在或访问码无效");
			}

			// 验证审核状态
			if (StpUtil.hasRole("admin")) {
				// 如果是管理员角色，则可以直接下载
				log.info("管理员角色，可以直接下载文件: {}", fileDO.getFileName());
			} else if ("pending".equals(fileDO.getAuditStatus())) {
				log.error("文件未通过审核，访问码: {},id:{}", accessCode, fileId);
				throw new ServiceException("文件未通过审核");
			} else if ("rejected".equals(fileDO.getAuditStatus())) {
				log.error("文件上传已被拒绝，访问码: {},id:{}", accessCode, fileId);
			}

			// 获取文件并验证
			File file = fileUtil.getFileByAccessCode(accessCode, fileDO);
			log.info("文件获取成功: {}", fileDO.getFileName());

			// 创建文件输入流
			inputStream = new FileInputStream(file);

			// 更新下载次数和使用次数
			fileDO.setDownloadCount(fileDO.getDownloadCount() + 1);
			fileDO.setUsedCount(fileDO.getUsedCount() + 1);
			updateById(fileDO);
			log.info("文件下载计数已更新，当前下载次数: {}", fileDO.getDownloadCount());

			// 确定文件的MIME类型
			String contentType = determineContentType(fileDO.getFileExt());

			// 创建包含文件信息和输入流的Map
			Map<String, Object> result = new HashMap<>();
			result.put("resource", new InputStreamResource(inputStream));
			result.put("fileName", fileDO.getFileName());
			result.put("fileSize", fileDO.getFileSize());
			result.put("contentType", contentType);

			// 添加记录
			String userId;
			try {
				userId = StpUtil.getLoginIdAsString();
			} catch (Exception e) {
				// 未登录就设置无登录id
				// userId = BaseConstant.DEFAULT_NOT_USER_DOWNLOAD_ID;
				userId = null;
			}
			RecordDO recordDO = RecordDO.builder()
					.genre("down")
					.fileId(fileDO.getId())
					.userId(userId)
					.ip(IP)
					.build();
			recordService.save(recordDO);

			return result;
		} catch (ServiceException e) {
			closeQuietly(inputStream);
			log.error("文件下载失败: {}", e.getMessage());
			throw e;
		} catch (Exception e) {
			closeQuietly(inputStream);
			log.error("文件下载异常", e);
			throw new ServiceException(ResultErrorCodeEnums.FILE_DOWNLOAD_FAILED);
		}
	}

	@Override
	public void validAccessCode(String accessCode) {
		if (accessCode == null || accessCode.isEmpty()) {
			throw new ServiceException(ResultErrorCodeEnums.REQUEST_PARAM_MISSING);
		}
		// 转成大写
		accessCode = accessCode.toUpperCase();
		LambdaQueryWrapper<FileDO> queryWrapper = new LambdaQueryWrapper<FileDO>()
				.eq(FileDO::getAccessCode, accessCode);
		boolean b = count(queryWrapper) > 0;
		if (!b) {
			log.error("访问码无效: {}", accessCode);
			throw new ServiceException(ResultErrorCodeEnums.DATA_NOT_FOUND);
		}
		log.info("访问码有效: {}", accessCode);
	}

	@Override
	public List<Map<String, Object>> getArchiveContents(String accessCode, String fileId) {
		log.info("获取压缩包内容，访问码: {}, 文件ID: {}", accessCode, fileId);
		try {
			// 验证访问码和文件ID
			if (accessCode == null || accessCode.isEmpty()) {
				throw new ServiceException("访问码不能为空");
			}

			// 构建查询条件
			LambdaQueryWrapper<FileDO> queryWrapper = new LambdaQueryWrapper<FileDO>()
					.eq(FileDO::getAccessCode, accessCode);

			// 如果提供了文件ID，则添加到查询条件
			if (fileId != null && !fileId.isEmpty()) {
				queryWrapper.eq(FileDO::getId, fileId);
			}

			// 查询文件信息
			FileDO fileDO = getOne(queryWrapper);

			if (fileDO == null) {
				log.error("文件不存在，访问码: {}, 文件ID: {}", accessCode, fileId);
				throw new ServiceException("文件不存在或访问码无效");
			}

			// 验证文件类型是否为压缩包
			if (!"archive".equals(fileDO.getFileType())) {
				log.error("文件不是压缩包类型，文件类型: {}", fileDO.getFileType());
				throw new ServiceException("该文件不是压缩包类型");
			}

			// 获取文件并验证
			File file = fileUtil.getFileByAccessCode(accessCode, fileDO);
			log.info("压缩文件获取成功: {}", fileDO.getFileName());

			// 解析压缩包内容
			return parseArchiveContents(file, fileDO.getFileExt());
		} catch (ServiceException e) {
			log.error("获取压缩包内容失败: {}", e.getMessage());
			throw e;
		} catch (Exception e) {
			log.error("获取压缩包内容异常", e);
			throw new ServiceException("解析压缩包内容失败: " + e.getMessage());
		}
	}
	
	@Override
	@Transactional
	public Map<String, Object> preview(String accessCode, String fileId, String IP) {
		log.info("收到预览文件请求，访问码: {}, 文件ID: {}", accessCode, fileId);
		InputStream inputStream = null;
		try {
			// 验证访问码
			if (accessCode == null || accessCode.isEmpty()) {
				throw new ServiceException("访问码不能为空");
			}

			// 构建查询条件
			LambdaQueryWrapper<FileDO> queryWrapper = new LambdaQueryWrapper<FileDO>()
					.eq(FileDO::getAccessCode, accessCode);

			// 如果提供了文件ID，则添加到查询条件
			if (fileId != null && !fileId.isEmpty()) {
				queryWrapper.eq(FileDO::getId, fileId);
			}

			// 查询文件信息
			FileDO fileDO = getOne(queryWrapper);

			if (fileDO == null) {
				log.error("文件不存在，访问码: {}, 文件ID: {}", accessCode, fileId);
				throw new ServiceException("文件不存在或访问码无效");
			}

			// 获取文件并验证
			File file = fileUtil.getFileByAccessCode(accessCode, fileDO);
			log.info("文件获取成功: {}", fileDO.getFileName());

			// 创建文件输入流
			inputStream = new FileInputStream(file);

			// 注意：预览不增加下载次数和使用次数
			log.info("文件预览请求，不计入下载次数: {}", fileDO.getFileName());

			// 确定文件的MIME类型
			String contentType = determineContentType(fileDO.getFileExt());

			// 创建包含文件信息和输入流的Map
			Map<String, Object> result = new HashMap<>();
			result.put("resource", new InputStreamResource(inputStream));
			result.put("fileName", fileDO.getFileName());
			result.put("fileSize", fileDO.getFileSize());
			result.put("contentType", contentType);

			// 添加预览记录，但不增加下载计数
			String userId;
			try {
				userId = StpUtil.getLoginIdAsString();
			} catch (Exception e) {
				// 未登录就设置无登录id
				userId = null;
			}
			RecordDO recordDO = RecordDO.builder()
					.genre("preview")
					.fileId(fileDO.getId())
					.userId(userId)
					.ip(IP)
					.build();
			recordService.save(recordDO);

			return result;
		} catch (ServiceException e) {
			closeQuietly(inputStream);
			log.error("文件预览失败: {}", e.getMessage());
			throw e;
		} catch (Exception e) {
			closeQuietly(inputStream);
			log.error("文件预览异常", e);
			throw new ServiceException(ResultErrorCodeEnums.FILE_DOWNLOAD_FAILED);
		}
	}

	/**
	 * 解析压缩包内容
	 *
	 * @param file 压缩文件
	 * @param fileExt 文件扩展名
	 * @return 压缩包内容列表
	 */
	private List<Map<String, Object>> parseArchiveContents(File file, String fileExt) {
		List<Map<String, Object>> contents = new ArrayList<>();
		FileInputStream fis = null;
		ArchiveInputStream archiveInputStream = null;

		try {
			fis = new FileInputStream(file);
			
			// 根据文件扩展名选择合适的解析方式
			switch (fileExt.toLowerCase()) {
				case "zip":
					archiveInputStream = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.ZIP, fis);
					break;
				case "tar":
					archiveInputStream = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.TAR, fis);
					break;
				case "jar":
					archiveInputStream = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.JAR, fis);
					break;
				case "7z":
				case "rar":
				case "gz":
				case "gzip":
					// 这些格式需要特殊处理，目前简化处理
					log.warn("暂不完全支持{}格式解析，将尝试基本解析", fileExt);
					try {
						// 尝试使用通用方式解析
						archiveInputStream = new ArchiveStreamFactory().createArchiveInputStream(fis);
					} catch (Exception e) {
						log.error("无法解析{}格式压缩文件: {}", fileExt, e.getMessage());
						throw new ServiceException("暂不支持" + fileExt + "格式完整解析");
					}
					break;
				default:
					// 尝试自动检测格式
					try {
						archiveInputStream = new ArchiveStreamFactory().createArchiveInputStream(fis);
						log.info("自动检测到压缩文件格式并成功解析");
					} catch (Exception e) {
						log.error("不支持的压缩文件格式: {}", fileExt);
						throw new ServiceException("不支持的压缩文件格式: " + fileExt);
					}
			}

			// 遍历压缩包内容
			ArchiveEntry entry;
			while ((entry = archiveInputStream.getNextEntry()) != null) {
				if (!entry.isDirectory()) {
					Map<String, Object> entryInfo = new HashMap<>();
					entryInfo.put("name", entry.getName());
					entryInfo.put("size", entry.getSize());
					entryInfo.put("isDirectory", false);
					entryInfo.put("lastModified", entry.getLastModifiedDate());
					contents.add(entryInfo);
				} else {
					// 目录项
					Map<String, Object> entryInfo = new HashMap<>();
					entryInfo.put("name", entry.getName());
					entryInfo.put("size", 0);
					entryInfo.put("isDirectory", true);
					entryInfo.put("lastModified", entry.getLastModifiedDate());
					contents.add(entryInfo);
				}
			}

			return contents;
		} catch (Exception e) {
			log.error("解析压缩包内容失败", e);
			throw new ServiceException("解析压缩包内容失败: " + e.getMessage());
		} finally {
			// 关闭资源
			closeQuietly(archiveInputStream);
			closeQuietly(fis);
		}
	}

	/**
	 * 根据文件扩展名确定内容类型
	 *
	 * @param fileExt 文件扩展名
	 * @return 内容类型
	 */
	private String determineContentType(String fileExt) {
		if (fileExt == null || fileExt.isEmpty()) {
			return "application/octet-stream";
		}

		return switch (fileExt.toLowerCase()) {
			case "pdf" -> "application/pdf";
			case "doc" -> "application/msword";
			case "docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
			case "xls" -> "application/vnd.ms-excel";
			case "xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
			case "png" -> "image/png";
			case "jpg", "jpeg" -> "image/jpeg";
			case "gif" -> "image/gif";
			case "txt" -> "text/plain";
			case "html" -> "text/html";
			// 音频文件类型
			case "mp3" -> "audio/mpeg";
			case "wav" -> "audio/wav";
			case "ogg" -> "audio/ogg";
			case "flac" -> "audio/flac";
			case "aac" -> "audio/aac";
			// 视频文件类型
			case "mp4" -> "video/mp4";
			case "avi" -> "video/x-msvideo";
			case "mov" -> "video/quicktime";
			case "wmv" -> "video/x-ms-wmv";
			case "mkv" -> "video/x-matroska";
			case "webm" -> "video/webm";
			// 压缩文件类型
			case "zip" -> "application/zip";
			case "rar" -> "application/x-rar-compressed";
			default -> "application/octet-stream";
		};
	}

	/**
	 * 确保 FileDO 的 accessCode 是唯一的
	 *
	 * @param fileDO 文件
	 */
	private void ensureUniqueAccessCode(FileDO fileDO) {
		int maxAttempts = 4;
		for (int i = 0; i < maxAttempts; i++) {
			LambdaQueryWrapper<FileDO> queryWrapper = new LambdaQueryWrapper<>();
			queryWrapper.eq(FileDO::getAccessCode, fileDO.getAccessCode());

			if (count(queryWrapper) > 0) {
				fileDO.setAccessCode(fileUtil.generateAccessCode());
			} else {
				log.debug("AccessCode 已确保唯一，值为: {}", fileDO.getAccessCode());
				return;
			}
		}

		// 如果经过多次尝试仍无法生成唯一值，抛出异常
		log.error("无法生成唯一的 AccessCode，当前值: {}", fileDO.getAccessCode());
		throw new ServiceException(ResultErrorCodeEnums.DATABASE_RESOURCE_ERROR);
	}

	/**
	 * 安全地关闭流
	 *
	 * @param stream inputStream
	 */
	private void closeQuietly(InputStream stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				log.error("关闭输入流失败", e);
			}
		}
	}

	/**
	 * 安全地关闭归档流
	 *
	 * @param stream ArchiveInputStream
	 */
	private void closeQuietly(ArchiveInputStream stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				log.error("关闭归档流失败", e);
			}
		}
	}
}